package cn.ibizlab.core.authentication.aspect;

import cn.ibizlab.core.authentication.domain.AuthLogin;
import cn.ibizlab.util.domain.LoggingEvent;
import cn.ibizlab.util.errors.BadRequestAlertException;
import cn.ibizlab.util.mapper.LoggingEventMapper;
import cn.ibizlab.util.web.IPUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.zalando.problem.Status;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static cn.ibizlab.core.authentication.constants.AuthenticationConstant.*;

/**
 * 登录日志切面：记录用户登录请求及响应结果
 */
@Slf4j
@Aspect
@Component
@Order(50)
@ConditionalOnExpression("'${ibiz.auth.log.enable:true}'.equals('true')")
public class SysAuthLogAspect {

    private SimpleDateFormat sdf = new SimpleDateFormat(DATETIME_FORMAT_YYYYMMDDHHMMSS);


    private static final String LOGIN_AUTH_SYSTEM_UAA = "UAA";
    private static final String LOGIN_AUTH_ENTITY = "AUTH_LOGIN";
    private static final String LOGIN_AUTH_ACTION = "login";
    private static final String LOG_LEVEL_INFO = "INFO";
    private static final String LOG_LEVEL_ERROR = "ERROR";
    private static final String LOG_REQUEST_PARAM = "param";
    private static final String LOG_RESPONSE_PARAM = "result";
    private static final String LOG_PARAM_CODE = "code";
    private static final String LOG_PARAM_MESSAGE = "message";
    private static final int LOGIN_SUCCESS_FLAG = 1;
    private static final int LOGIN_ERROR_FLAG = 0;

    /**
     * 建立用户登录日志记录切面，将登录新增数据内容记录审计日志内
     *
     * @param point
     */
    @Around(value ="execution(* cn.ibizlab.core.authentication.service.AuthLoginService.login(..))")
    public Object login(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        try {
            result = point.proceed();
        } catch (Exception e) {
            result = e;
            throw e;
        } finally {
            saveLog(point.getArgs(), result);
        }
        return result;
    }

    /**
     * @param params 前端传后台的对象参数
     * @param result 执行login 接口后返回的对象
     */
    private void saveLog(Object[] params, Object result){
        try {
            if (ObjectUtils.isEmpty(params) || ObjectUtils.isEmpty(result)) {
                return;
            }
            Object param = params[0];
            if (!(param instanceof AuthLogin)) {
                return;
            }

            AuthLogin requestParam = (AuthLogin) param;
            String userName = requestParam.getLoginName();
            String ipAddress = IPUtils.getIpAddr(null);
            String fullMessage = fullMessage(requestParam,result);
            Long currentTimeMillis = System.currentTimeMillis();
            String loginTime = sdf.format(currentTimeMillis);

            LoggingEvent log = new LoggingEvent().setCallerFilename(LOGIN_AUTH_SYSTEM_UAA).setCallerClass(LOGIN_AUTH_ENTITY)
                    .setCallerMethod(LOGIN_AUTH_ACTION).setTimeStamp(currentTimeMillis).setThreadName(ipAddress);

            //登录成功
            if (result instanceof AuthLogin) {
                AuthLogin resultParam = (AuthLogin) result;
                     log.setArg0(userName)
                        .setArg1(resultParam.getUser() != null? resultParam.getUser().getDisplayName(): userName)
                        .setReferenceFlag(LOGIN_SUCCESS_FLAG)
                        .setLevelString(LOG_LEVEL_INFO)
                        .setLoggerName(String.format("用户[%1$s]于[%2$s]登录成功", userName, loginTime))
                        .setFormattedMessage(fullMessage);

            }
            //登录失败
            else if (result instanceof Exception) {
                     log.setArg0(userName)
                        .setArg1(userName)
                        .setReferenceFlag(LOGIN_ERROR_FLAG)
                        .setLevelString(LOG_LEVEL_ERROR)
                        .setFormattedMessage(((Exception) result).getMessage())
                        .setLoggerName(String.format("用户[%1$s]于[%2$s]登录失败", userName, loginTime))
                        .setFormattedMessage(fullMessage);
            }

            cacheMap.add(log);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("记录用户登录日志发生异常：{}",e.getMessage());
        }
    }

    /**
     * 获取完整的登录信息（含请求参数与响应结果）
     *
     * @return
     */
    private String fullMessage(AuthLogin requestParam, Object resultParam) {
        JSONObject params = new JSONObject();
        //登录请求入参
        JSONObject requestParams = null;
        if (requestParam != null) {
            requestParam.setPassword(null);
            requestParams = JSONObject.parseObject(JSON.toJSONString(requestParam));
        }
        params.put(LOG_REQUEST_PARAM, requestParams);

        //记录异常响应请求
        JSONObject resultParams = new JSONObject();
        if (resultParam != null && resultParam instanceof Exception) {
            Exception exception = (Exception) resultParam;
            resultParams.put(LOG_PARAM_CODE, exception instanceof BadRequestAlertException ? Status.BAD_REQUEST.getStatusCode() : 500);
            resultParams.put(LOG_PARAM_MESSAGE, exception.getMessage());
        }
        params.put(LOG_RESPONSE_PARAM, !ObjectUtils.isEmpty(resultParams) ? resultParams : null);
        return JSON.toJSONString(params);
    }


    private static List<LoggingEvent> cacheMap = Collections.synchronizedList(new ArrayList());
    @Autowired
    private LoggingEventMapper loggingEventMapper;
    private Boolean save2db;
    @Scheduled(
            fixedRate = 10000L
    )
    private void saveAudit() {
        if (cacheMap.size() > 0) {
            log.info(String.format("正在保存登录日志数据，当前审计集合数量为[%s]", cacheMap.size()));
            List<LoggingEvent> temp = new ArrayList();
            if (cacheMap.size() < 500) {
                temp.addAll(cacheMap);
            } else {
                temp.addAll(cacheMap.subList(0, 500));
            }

            if (this.getSave2db()) {
                LoggingEvent.saveBatch(temp);
            }

            cacheMap.removeAll(temp);
            log.info(String.format("保存完成，当前登录日志数量为[%s]", cacheMap.size()));
        }

    }

    private Boolean getSave2db() {
        if (this.save2db == null) {
            try {
                this.save2db = this.loggingEventMapper.checkLoggingEvent() == 0;
            } catch (Exception var2) {
                this.save2db = false;
            }
        }

        return this.save2db;
    }

}
